#region MIT License

/*
 *  Copyright (c) 2010 Nathan Palmer
 *  http://www.nathanpalmer.com/
 * 
 *  Permission is hereby granted, free of charge, to any person obtaining a copy
 *  of this software and associated documentation files (the "Software"), to deal
 *  in the Software without restriction, including without limitation the rights
 *  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *  copies of the Software, and to permit persons to whom the Software is
 *  furnished to do so, subject to the following conditions:
 * 
 *  The above copyright notice and this permission notice shall be included in
 *  all copies or substantial portions of the Software.
 * 
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *  THE SOFTWARE.
 */

#endregion

using System.Collections.Generic;
using System.Collections.Specialized;
using System.Reflection;
using System.Threading;
using Castle.MicroKernel.Registration;
using Castle.Windsor;
using Castle.Windsor.Configuration.Interpreters;
using Common.Logging;
using Quartz;
using Quartz.Impl;
using Rhino.ServiceBus;
using Rhino.ServiceBus.Impl;
using Rhino.ServiceBus.Internal;
using SimpleScheduler.Core.Common;
using SimpleScheduler.Core.Domain;
using SimpleScheduler.Server.Listener;
using SimpleScheduler.Server.Plugin;

namespace SimpleScheduler.Server
{
    public class SimpleSchedulerService : IJobListener
    {
        private IScheduler scheduler;
        private ILog log;
        //private WindsorContainer container;
        internal static IStartableServiceBus bus;
        private JobLogger jobLogger;
        private JobChangeNotifier jobChangeNotifier;
        internal static Dictionary<string, JobState> jobStateDictionary;
        internal static ReaderWriterLockSlim jobStatePadLock = new ReaderWriterLockSlim();

        public string Name
        {
            get { return "ServerListener"; }
        }

        public SimpleSchedulerService()
        {
            log = LogManager.GetLogger("ServerLog");

            jobStateDictionary = new Dictionary<string, JobState>();

            var properties = new NameValueCollection();
            properties["quartz.scheduler.instanceName"] = System.Environment.MachineName;

            //properties["quartz.jobStore.type"] = "SimpleScheduler.Server.Services.XmlJobStore, SimpleScheduler.Server";            
            //properties["quartz.jobStore.type"] = "Quartz.Impl.AdoJobStore.JobStoreTX, Quartz";
            //properties["quartz.jobStore.dataSource"] = "default";
            //properties["quartz.jobStore.tablePrefix"] = "QRTZ_";
            //properties["quartz.jobStore.clustered"] = "false";
            //properties["quartz.jobStore.lockHandler.type"] = "Quartz.Impl.AdoJobStore.UpdateLockRowSemaphore, Quartz";
            //properties["quartz.jobStore.driverDelegateType"] = "Quartz.Impl.AdoJobStore.SqlServerDelegate, Quartz";
            //properties["quartz.dataSource.default.connectionString"] = @"Server=.\SQLEXPRESS;Database=SimpleScheduler;Integrated Security=SSPI";
            //properties["quartz.dataSource.default.provider"] = "SqlServer-20";
            //properties["quartz.jobStore.useProperties"] = "false";

            // set thread pool info
            properties["quartz.threadPool.type"] = "Quartz.Simpl.SimpleThreadPool, Quartz";
            properties["quartz.threadPool.threadCount"] = "5";
            properties["quartz.threadPool.threadPriority"] = "Normal";

            // job initialization plugin handles our xml reading, without it defaults are used
            properties["quartz.plugin.xml.type"] = typeof(XmlJobPlugin).AssemblyQualifiedName;
            properties["quartz.plugin.xml.folderPath"] = "~/Jobs/";

            // set job history plugin
            properties["quartz.plugin.history.type"] = "Quartz.Plugin.History.LoggingJobHistoryPlugin, Quartz";

            // set remoting expoter
            properties["quartz.scheduler.exporter.type"] = "Quartz.Simpl.RemotingSchedulerExporter, Quartz";
            properties["quartz.scheduler.exporter.port"] = "555";
            properties["quartz.scheduler.exporter.bindName"] = "QuartzScheduler";
            properties["quartz.scheduler.exporter.channelType"] = "tcp";

            //properties["quartz.scheduler.proxy"] = "true";
            //properties["quartz.scheduler.proxy.address"] = "tcp://" + ServerName + ":" + Port + "/QuartzScheduler";

            // First we must get a reference to a scheduler
            ISchedulerFactory sf = new StdSchedulerFactory(properties);
            scheduler = sf.GetScheduler();
        }

        public void Start()
        {            
            log.Trace("Starting");

            //container = new WindsorContainer(new XmlInterpreter());
            //container.Kernel.AddFacility("rhino.esb", new RhinoServiceBusFacility());
            //container.Register(
            //    AllTypes.FromAssembly(Assembly.GetExecutingAssembly())
            //        .BasedOn(typeof (IMessageConsumer))
            //    );

            //bus = container.Resolve<IStartableServiceBus>();
            //bus.Start();

            scheduler.AddGlobalJobListener(this);

            jobLogger = new JobLogger();            
            scheduler.AddGlobalJobListener(jobLogger);
            scheduler.AddSchedulerListener(jobLogger);

            jobChangeNotifier = new JobChangeNotifier(scheduler, bus);
            scheduler.AddSchedulerListener(jobChangeNotifier);
            //scheduler.AddSchedulerListener(xml);

            scheduler.Start();
        }

        public void Stop()
        {
            log.Trace("Stopping");

            scheduler.Shutdown();

            if (bus != null)
            {
                lock (bus)
                {

                    bus.Dispose();
                    bus = null;
                }
            }
        }

        public void JobToBeExecuted(JobExecutionContext context)
        {
            var key = context.JobDetail.Key.Name;
            UpdateJobState(context.JobDetail.Name, context.JobDetail.Group, JobState.Running);
        }

        public void JobWasExecuted(JobExecutionContext context, JobExecutionException jobException)
        {
            var key = context.JobDetail.Key.Name;
            UpdateJobState(context.JobDetail.Name, context.JobDetail.Group, JobState.Idle);
        }

        public void JobExecutionVetoed(JobExecutionContext context)
        {
            //TODO: Find out exactly when this occurs
        }

        private void UpdateJobState(string JobName, string GroupName, JobState State)
        {
            var Key = JobHelpers.GenerateJobKey(JobName, GroupName);

            // Update the job dictionary
            jobStatePadLock.EnterWriteLock();
            try
            {
                if (jobStateDictionary.ContainsKey(Key))
                {
                    jobStateDictionary[Key] = State;
                }
                else
                {
                    jobStateDictionary.Add(Key, State);
                }
            }
            finally
            {
                jobStatePadLock.ExitWriteLock();
            }

            // Notify any subscribers of the change
            if (bus != null)
            {
                lock (bus)
                {

                    log.Trace(string.Format("Sending {0} message for {1}", State.ToString("g"), Key));

                    var message = new JobStateChange
                                      {
                                          JobName = JobName,
                                          GroupName = GroupName,
                                          State = State
                                      };

                    bus.Notify(message);

                }
            }
        }

        internal static JobState GetJobState(string Key)
        {
            jobStatePadLock.EnterReadLock();
            try
            {
                if (jobStateDictionary.ContainsKey(Key))
                {
                    return jobStateDictionary[Key];
                }

                return JobState.Idle;
            }
            finally
            {
                jobStatePadLock.ExitReadLock();
            }
        }
    }
}